﻿/* ===============================================================================  
 * Copyright (c) 2010 Acid Frameworks
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * 
 * =============================================================================== */

using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Windows.Forms;

namespace Acid.Network.Udp
{
    public delegate void OnUdpPacket(Listener listener, Packet packet);
    public delegate void OnUdpError (Listener listener, string message);
   
    /// <summary>
    /// UDP Listener.
    /// </summary>
    public class Listener : IDisposable
    {
        private delegate void DispatchPacketDelegate(Listener listener, Packet packet);

        private DispatchPacketDelegate dispatchPacket;
        private Control                control;
        private UdpClient              client;
        private IPEndPoint             endPoint;
        private int                    port;
        private Thread                 thread;
        private bool                   running;
        public  event      OnUdpPacket OnPacket;
        public  event      OnUdpError  OnError;

        /// <summary>
        /// Creates a new listener with a default receiveBufferSize of 8192. Listener will start listening as soon as the object is instantiated.
        /// </summary>
        /// <param name="port">The port to listen on.</param>
        public Listener(int port) : this(null, port, 8192) { }

        /// <summary>
        /// Creates a new listener and sets up a event callback from the listening thread back to the main thread. 
        /// </summary>
        /// <param name="port">The port to listen on.</param>
        public Listener(Control control, int port) : this(control, port, 8192) { }

        /// <summary>
        /// Creates a UDP Listener.  and sets up a event callback from the listening thread back to the main thread. Listener will begin 
        /// listening as soon as the object is instantiated.
        /// </summary>
        /// <param name="port">The port to listen on.</param>
        /// <param name="receiveBufferSize">(Redundant) The expected max size of the incoming data.</param>
        public Listener(Control control, int port, int receiveBufferSize)
        {
            this.control        = control;
            this.dispatchPacket = this.DispatchPacketHandler;
            this.port           = port;
            this.client         = new UdpClient();
            this.client.Client.ReceiveBufferSize = receiveBufferSize;
            this.endPoint       = new IPEndPoint(IPAddress.Any, this.port);
            this.client.Client.Bind(endPoint);
            this.thread         = new Thread(new ThreadStart(this.Runtime));
            this.running        = true;
            this.thread.Start();
        }

        #region Methods

        private void DispatchPacketHandler(Listener listener, Packet packet)
        {
            if (this.OnPacket != null)
            {
                this.OnPacket(listener, packet);
            }
        }

        private void Runtime()
        {
            while (this.running)
            {
                try
                {
                    byte[] data = client.Receive(ref this.endPoint);

                    Packet packet = Packet.Deserialize(data);

                    // If the control has been set, then we have a main thread execution
                    // context. This is typical for forms based application where the updating
                    // of the user interface is required. This by passes the need to create
                    // UpdateUI Deletes on the form itself. Thus hiding all the nasty requirements
                    // for the Thread management.
                    if (this.control != null)
                    {
                        this.control.Invoke(this.dispatchPacket, this, packet);
                    }
                    else
                    {
                        if (this.OnPacket != null)
                        {
                            this.OnPacket(this, packet);
                        }
                    }
                }
                catch(Exception e)
                {
                    if (this.OnError != null)
                    {
                        this.OnError(this, e.Message);
                    }
                }
            }
        }

        #endregion

        #region IDisposable Members

        private bool disposed = false;

        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (!disposed)
                {
                    this.running = false;

                    this.client.Close();

                    this.thread.Abort();
                }

                this.disposed = true;
            }
        }
        public void Dispose()
        {
            this.Dispose(true);
        }

        ~Listener()
        {
            this.Dispose(false);
        }

        #endregion
    }
}
